环形链表

题目描述:给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

img

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

img

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

img

进阶:你能用 O(1)(即,常量)内存解决此问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {

}
}

方法一:快慢指针

我们可以巧妙的使用快慢指针来模拟追击问题,只要存在环状结构,就一定可以追上。

  • 创建两个指针,P1(快指针)和P2(慢指针),并且同时指向链表的头结点。
  • 遍历链表,每遍历一个节点,指针P1向下移动两个节点,指针P2向下移动一个节点。(为什么移动两个节点:在快的快追上慢的时,他们之间一定只差1个或者2个节点,如果落后1个,那么下一次就能追上。如果落后2个,那么下一次就落后一个,再下一次就能追上,所以快指针可以设置成2)
  • 如果链表遍历结束之前,指针指向同一节点,则说明链表有环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head, fast = head.next;
while(slow != fast) {
if(fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}

复杂度分析:

  • 时间复杂度:遍历链表O(n)

  • 空间复杂度:除了两个指针,没有使用额外的存储空间,所以空间复杂度为O(1)

执行结果:通过

  • 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

  • 内存消耗:39.9 MB, 在所有 Java 提交中击败了59.68%的用户

方法二:哈希表

遍历链表,使用hashset来存储节点,每遍历一个节点,就判断hashset中是否已存在此节点,如果存在则说明节点有环。如果不存在,就将新节点存到hashset中,重复上述步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while(head != null) {
if(!set.add(head)) {
return true;
} else {
head = head.next;
}
}
return false;
}
}

复杂度分析:

  • 时间复杂度:O(n)

  • 空间复杂度:使用了额外的存储空间,所以空间复杂度为O(n)

执行结果:通过

  • 执行用时:4 ms, 在所有 Java 提交中击败了22.81%的用户
  • 内存消耗:40.3 MB, 在所有 Java 提交中击败了13.69%的用户